"""
    名称：
        英文名称：Module.SocketThread
        中文名称：Socket线程连接

    定义：
        【类】SocketThread       Socket线程连接
"""

# 导入相关模块
import threading                            # 线程模块
import socket                               # Socket模块
import select                               # Select模块
import queue                                # 队列模块
import json                                 # JSON模块
from Module import AppState                 # 自定义：APP状态（枚举）
from Module import AppLocalType             # 自定义：本机类型（枚举）


class SocketThread(threading.Thread):
    """
        Socket线程连接类

        继承自threading.Thread类

        方法：
            __init__：初始化
            run：线程执行程序
            __build_server：创建线程连接对象
            __clear_socket_list：清空连接对象
            __command：处理命令
            __send_command：发送命令
            __log_print：调试信息打印
            Send_Point：发送坐标点
            Connect_Black：连接执黑主机
            Close：关闭当前线程的连接对象
    """

    def __init__(self, app, ip, port):
        """
            线程初始化

            参数：
                app：主窗口对象（DisGobang.py中的APP对象）
                ip：绑定的IP
                port：绑定的端口
        """

        """
        利用继承threading库的Thread类实现线程控制
        """

        # 调用父类初始化方法进行对象初始化
        threading.Thread.__init__(self)
        # 新式父类初始化方式（不可用？？？？待测试）
        # super(SocketThread, self).__init__(self)

        """
        定义类的属性变量
        """

        # 定义本机IP
        self.__IP = ip
        # 定义端口
        self.__Port = port
        # select监听    list
        self.__Inputs = []
        self.__Outputs = []
        # 存放消息的队列，用于回发给客户端
        self.__MQ = queue.Queue()
        # 存储隶属对象（主窗体）
        self.__APP = app
        # 是否输出调试的信息
        self.__Debug = True

    def run(self):
        """
            线程默认执行函数
            start()->run()
            重写run()，让线程start()后执行此函数方法
        """

        # 打印调试信息
        self.__log_print(self.getName() + "+线程+启动线程....\n")

        """
        利用内部方法__build_server创建连接用的socket对象
        """

        # 创建连接对象
        self.__build_server()

        """
        线程主循环开始
        """

        # 临时变量：线程主循环执行次数
        select_n = 0
        # 开始循环监听
        while self.__Inputs:
            """
            记录主循环次数，方便调试
            """
            # 主循环执行次数+1
            select_n = select_n + 1
            # 打印调试信息
            self.__log_print(self.getName() + "+线程+线程循环循环监听中..##########开始 第" +
                             str(select_n) + "次..######\n")

            """
            通过判断监听线程的状态符决定
            是否清理连接对象
            并退出线程主循环
            从而让线程结束
            状态变更主要发生在对弈结束或用户主动关闭对弈
            此项操作会发生在收到下一个socket连接动作后方可执行，因为默认阻塞在select方法处。
            """
            # 如果线程状态为否，则不再继续监控
            if not self.__APP.ThreadState:
                # 打印调试信息
                self.__log_print(self.getName() + '+线程+线程状态标志为False，退出线程\n')
                # 清理连接
                self.__clear_socket_list()
                # 退出主循环
                break

            # 打印调试信息
            self.__log_print(self.getName() + "+线程+waiting....\n")

            """
            # 调用select去监听inputs中的socket对象状态，事件超时则返回三个空数组
            # 当你把inputs,outputs,exceptional(这里跟inputs共用)传给select()后，它返回3个新的list，
            # 我们上面将他们分别赋值为readable,writable,exceptional
            # 所有在readable list中的socket连接代表有数据可接收(recv),
            # 所有在writable list中的存放着你可以对其进行发送(send)操作的socket连接，
            # 当连接通信出现error时会把error写到exceptional列表中。
            """
            # 带超时 的 方式
            # readable, writable, exceptional = select.select(inputs, outputs, inputs, timeout)
            # 不带超时
            readable, writable, exceptional = select.select(
                self.__Inputs, self.__Outputs, self.__Inputs)
            # 打印调试信息
            self.__log_print(self.getName() + "+线程+Select有事件产生...\n")

            """
            如果select有新事件产生则首先判断返回值是否为空，如果开启超时，超时后，则返回三个空List
            """
            # When timeout reached , select return three empty lists
            # 时间超时则返回三个空的list
            if not (readable or writable or exceptional):
                # 打印调试信息
                self.__log_print(self.getName() + "+线程+Select监听超时，退出...\n")
                # 退出主循环
                break

            """
            如果有select有新事件产生，但是监听的List已经为空了则直接退出。
            """
            # 如果监听队列列表都为空，则直接退出
            if not (len(self.__Inputs) or len(self.__Outputs)):
                # 打印调试信息
                self.__log_print(self.getName() + "+线程+Select监听队列已经清空，退出...\n")
                # 退出主循环
                break

            """
            循环select监听到的读list
            """
            # 循环接收
            for s in readable:  # 每一个s就是有个socket
                """
                判断当前监听到的事件是否来自__UDP_Server
                其实此处用的是UDP协议，只有一个socket对象，所以可以不用判断
                """
                if s is self.__UDP_Server:
                    """
                    如果Socket对象已经关闭则直接退出循环，不再继续
                    """
                    # 如果已经关闭则跳过执行下一个
                    if s._closed:
                        # 打印调试信息
                        self.__log_print(self.getName() +
                                         '+线程+ 读队列中的对象S已经被关闭了，无法继续执行退出读循环\n')
                        # 退出循环
                        break

                    """
                    读取收到的信息，并且交给__command()方法处理
                    """
                    # 获取发送来的数据
                    data, addr = s.recvfrom(1024)
                    # 打印调试信息
                    self.__log_print(self.getName() + '+线程+GET，获取到消息：【 ' +
                                     data.decode('utf-8') + '】 来自(' + str(addr[0]) + ', ' + str(addr[1]) + ')\n')
                    # 打印调试信息
                    self.__log_print(self.getName() +
                                     "+线程+COMMAND+，交给命令处理方法处理\n")
                    # 处理收到的内容
                    self.__command(data.decode('utf-8'), addr)
                else:
                    # 打印调试信息
                    self.__log_print(self.getName() + '+线程+其他连接接收到的信息，不做处理\n')

            """
            循环select监听到的异常list，因为没有执行到所以暂时注释掉
            """
            # 循环异常
            # for s in exceptional:
            #     self.__log_print(self.getName() + "发生异常")
            #     self.__Inputs.remove(s)
            #     if s in self.__Inputs:
            #         self.__Inputs.remove(s)
            #     s.close()

            """
            循环select监听到的写list（发送）
            """
            # 循环发送
            for s in writable:
                """
                如果Socket对象已经关闭则直接退出主循环，不再继续
                """
                # 如果已经关闭则跳过执行下一个
                if s._closed:
                    # 打印调试信息
                    self.__log_print(self.getName() +
                                     '+线程+ 写队列中的对象S已经被关闭了，无法继续执行退出读循环\n')
                    # 退出循环
                    break
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '+线程+SEND，监听到要发送的对象，马上获取数据...\n')

                """
                判断队列中是否有消息要发送，无消息则跳过
                默认模式时通过get_nowait()获取消息，无消息抛异常后处理，但是VS code调试时会依旧会阻塞，不抛异常
                所以选择以下替代方案
                """
                # 判断队列是否为空
                if not self.__MQ.empty():  # 有消息
                    # 获取一条消息
                    # 消息格式：list对象[str,json_str] 分别为 【IP,DATA】
                    # ['192.168.0.104', '{"cmd": "blackready", "cmdstr": "Nickname:Else"}']
                    next_msg = self.__MQ.get_nowait()
                    # 获取发送信息的IP
                    ip = next_msg[0]
                    # 根据当前主机类型，获取发送信息的端口
                    if self.__APP.LocalType == AppLocalType.Black:  # 当前为执黑主机，则发送消到执白主机端口
                        port = self.__APP.WhitePort
                    elif self.__APP.LocalType == AppLocalType.White:  # 当前为执白主机，则发送消到执黑主机端口:
                        port = self.__APP.BlackPort
                    else:  # 当前还没决定是执黑还是执白，退出发送
                        # 退出循环
                        break
                    # 获取发送的消息体
                    data = next_msg[1]
                    # 发送数据
                    s.sendto(data.encode('utf-8'), (ip, port))
                    # 打印调试信息
                    self.__log_print(
                        self.getName() + "+线程+回发消息：【" + data + "】 到 " + ip + ":" + str(port) + '\n')
                else:  # 无消息存在
                    # 打印调试信息
                    self.__log_print(self.getName() +
                                     "+线程+SEND，队列中没有待发送的消息，从outputs监听list中清理发送socket对象\n")
                    # 将此Socket连接对象从监控列表中清除
                    self.__Outputs.remove(s)
                # ################### #################### ####################
                # 以下内容是正常模式，但是在vs code中存在不触发queue.Empty异常导致get_nowait函数依旧阻塞
                # try:
                #     # 尝试从队列中获取当前socket的消息队列
                #     # 消息格式：list对象[str,json_str] 分别为 【IP,DATA】
                #     # ['192.168.0.104', '{"cmd": "blackready", "cmdstr": "Nickname:Else"}']
                #     # 未转码
                #     next_msg = self.__MQ.get_nowait()
                # except queue.Empty:
                #     self.__log_print(self.getName() + "+线程+SEND，队列中没有待发送的消息，从outputs监听list中清理发送socket对象\n")
                #     self.__Outputs.remove(s)
                # else:
                #     # 获取发送的基本信息
                #     ip = next_msg[0]
                #     if self.__APP.AppState == AppState.BlackReady:  # 当前为执黑主机，则发送消到执白主机端口
                #         port = self.__APP.WhitePort
                #     else:
                #         port = self.__APP.BlackPort
                #     data = next_msg[1]
                #     # 发送数据
                #     s.sendto(data.encode('utf-8'), (ip, port))
                #     self.__log_print(self.getName() + "+线程+回发消息：【", data, "】 到 ", ip, ":", port, '\n')

            # 打印调试信息
            self.__log_print(self.getName() + '+线程+监听：完成第%d次\n' % select_n)

        # 打印调试信息
        self.__log_print(self.getName() + '+线程+主循环退出，线程结束\n')

    def __build_server(self):
        """
            创建线程链接对象
        """

        # 打印调试信息
        self.__log_print(self.getName() + "+线程+开始创建socket对象UDP\n")

        """
        创建UDP协议的Socket对象，并讲对象放入监听List
        """

        # 创建socket对象
        self.__UDP_Server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # self.__UDP_Server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.__UDP_Server.bind((self.__IP, self.__Port))
        # 非阻塞
        self.__UDP_Server.setblocking(False)
        # 加入select监听list inputs
        self.__Inputs.append(self.__UDP_Server)

        # 打印调试信息
        self.__log_print(self.getName() + "+线程+socket对象创建成功并加入监听list....\n")

    def __clear_socket_list(self):
        """
            清理连接对象
        """

        """
        清空__Inputs和__Outputs中的所有连接对象
        """

        # 清空inputs列表
        self.__log_print("-clear-正在清理inputs列表中的socket\n")
        # 关闭连接对象
        for s in self.__Inputs:
            if s and (not s._closed):
                self.__log_print("-clear-关闭\n")
                s.shutdown(socket.SHUT_RDWR)
                s.close()
        # 清空list
        self.__Inputs.clear()
        # 打印调试信息
        self.__log_print("-clear-input清理完毕\n")
        # 清空outputs列表
        self.__log_print("-clear-正在清理outputs列表中的socket\n")
        # 关闭连接对象
        for s in self.__Outputs:
            if s and (not s._closed):
                self.__log_print("-clear-关闭\n")
                s.shutdown(socket.SHUT_RDWR)
                s.close()
        # 清空list
        self.__Outputs.clear()
        # 打印调试信息
        self.__log_print("-clear-output清理完毕\n")

    def __command(self, data, addr):
        """
            处理cmd命令并进行相应的执行

            参数:
                data：收到的消息主体，json格式
                addr：发送来的请求地址
            data:（类型：dict，格式：{"cmd": "Connect", "args": {"nickname": "11111"}}）
                cmd：命令（类型：str）
                    Search:执白全网搜索执黑主机，群发的命令
                    BlackReady:执黑主机AppState=BlackReady时返回的确认命令
                    Connect:连接到执黑主机
                    ConnectYes:通知执白主机，连接成功
                    ConnectNo:通知执白主机，当前已经非连接状态了
                    Running:通知执黑主机，准备就绪开始对弈
                    RunningNo:通知执黑主机，无法准备就绪对弈
                    Play:下子信息
                args：要处理命令的参数（类型：dict，格式：{"nickname": "11111"} or {"x":7, "y":8}）
                    None:没有要处理的信息（Null）
                    nickname:昵称信息
                    x:坐标x的索引0-14
                    y:坐标y的索引0-14
        """

        """
        获取接收到的消息，并获取命令信息及参数
        """

        # 获取命令
        cmd = json.loads(data)
        # 获取 来源地址 不需要处理端口，端口采用默认端口发送根据 当前执黑or执白判断
        ip = addr[0]
        # 打印调试信息
        self.__log_print(self.getName() + "【命令】待处理命令：" + cmd["cmd"] + '\n')

        """
        处理命令
        """

        # 接收到的消息中无名为"cmd"的key
        if 'cmd' not in cmd:
            return

        # 根据命令进行相应处理
        if cmd["cmd"] == 'Search':  # 搜索
            """
            方向：执白主机---->执黑主机
            执行方：执黑主机
            """

            """
            判断当前主机状态，执黑准备状态则返回，否则不做任何处理
            """
            # 如果当前为执黑准备状态则返回BlackReady命令，告诉客户端
            if self.__APP.AppState == AppState.BlackReady:
                """
                状态符合要求，创建命令
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：当前主机BlackReady状态，可以返回BlackReady状态命令！\n')
                # 创建命令
                cmd = dict(cmd='BlackReady', args=dict(
                    nickname=self.__APP.Nickname))
                jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：创建BlackReady命令：' + jsoncmd + '\n')

                """
                将命令生成消息体存入消息队列中
                监听对象也放入__Outputs
                """
                # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
                self.__MQ.put([ip, jsoncmd])
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')
                # 加入select监听list outputs
                self.__Outputs.append(self.__UDP_Server)
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：将socket连接加入outputs对象列表\n')
            else:
                """
                状态不符合要求，不做处理
                """
                self.__log_print(self.getName() +
                                 '【命令】正在处理：接收到Search命令，但是当前非BlackReady状态，不返回任何数据\n')
        elif cmd["cmd"] == 'BlackReady':  # 准备就绪
            """
            方向：执白主机<----执黑主机
            执行方：执白主机
            """

            """
            收到此命令则将此主机添加到执黑主机列表并刷新列表
            """
            # 打印调试信息
            self.__log_print(self.getName() +
                             '【命令】正在处理：发现BlackReady状态主机，存入List,并在ListBox显示\n')
            # 添加新执黑主机到列表中备选
            self.__APP.Assisted.Black_List_Add([ip, cmd["args"]['nickname']])
        elif cmd["cmd"] == 'Connect':  # 连接请求
            """
            方向：执白主机---->执黑主机
            执行方：执黑主机
            """

            """
            执白主机发送连接请求，判断当前状态后，符合要求标记运行状态
            回发连接成功命令
            """
            # 如果当前为执黑准备状态则返回ConnectYes命令，将本机状态改为Running并告诉客户端连接成功
            # 如果当前不是执黑准备状态则返回ConnectNo命令
            if self.__APP.AppState == AppState.BlackReady:
                """
                当前状态为准备就绪
                则
                    修改本机状态为Running
                    修改对弈状态为True
                    修改对手IP
                    修改对手昵称
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：当前主机BlackReady状态，可以返回ConnectYes状态命令！\n')
                # 设置本机为Running状态
                self.__APP.AppState = AppState.Running
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】正在处理：设置本机状态为Running\n')
                # 设置当前对弈状态
                self.__APP.IsPlay = True
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】当前执黑，默认对弈状态打开\n')
                # 设置当前对弈的对手(IP)
                self.__APP.OpponentIP = ip
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执黑，设置对手IP:' + ip + '\n')
                # 设置当前对弈的对手(昵称)
                self.__APP.OpponentNickname = cmd["args"]['nickname']
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执黑，设置对手昵称:' + cmd["args"]['nickname'] + '\n')

                """
                创建命令
                """
                # 创建命令
                cmd = dict(cmd='ConnectYes', args=dict(
                    nickname=self.__APP.Nickname))
                jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
                self.__log_print(self.getName() +
                                 '【命令】正在处理：创建ConnectYes命令：' + jsoncmd + '\n')

                """
                将命令生成消息体存入消息队列中
                """
                # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
                self.__MQ.put([ip, jsoncmd])
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')
            else:
                """
                当前状态为不符合要求
                则返回连接失败ConnectNo命令
                """

                """
                 创建命令
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：当前主机不是BlackReady状态，返回ConnectNo状态命令！\n')
                # 创建命令
                cmd = dict(cmd='ConnectNo', args=dict(
                    nickname=self.__APP.Nickname))
                jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
                self.__log_print(self.getName() +
                                 '【命令】正在处理：创建ConnectNo命令：' + jsoncmd + '\n')

                """
                将命令生成消息体存入消息队列中
                """
                # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
                self.__MQ.put([ip, jsoncmd])
                self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')

            """
            监听对象也放入__Outputs
            """
            # 加入select监听list outputs
            self.__Outputs.append(self.__UDP_Server)
            # 打印调试信息
            self.__log_print(self.getName() +
                             '【命令】正在处理：将socket连接加入outputs对象列表\n')
        elif cmd["cmd"] == 'ConnectYes':  # 连接请求成功
            """
            方向：执白主机<----执黑主机
            执行方：执白主机
            """

            """
            对方主机返回了连接成功，并且如果本机为WhiteReady，则将本机状态改为Running并告诉客户端连接成功
            """
            # 否则返回RunningNo，表示连接失败，可以恢复状态
            if self.__APP.AppState == AppState.WhiteReady:
                """
                当前状态为准备就绪
                则
                    修改本机状态为Running
                    修改对弈状态为False
                    修改对手IP
                    修改对手昵称
                    初始化棋盘和对弈信息
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：当前主机是WhiteReady状态，返回Running状态命令！\n')
                # 设置本机为Running状态
                self.__APP.AppState = AppState.Running
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】正在处理：设置本机状态为Running\n')
                # 设置当前对弈状态
                self.__APP.IsPlay = False
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】当前执白，默认对弈状态关闭\n')
                # 设置当前对弈的对手(IP)
                self.__APP.OpponentIP = ip
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执白，设置对手IP:' + ip + '\n')
                # 设置当前对弈的对手(昵称)
                self.__APP.OpponentNickname = cmd["args"]['nickname']
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执白，设置对手昵称:' + cmd["args"]['nickname'] + '\n')

                """
                创建命令
                """
                # 创建命令
                cmd = dict(cmd='Running', args=dict(
                    nickname=self.__APP.Nickname))
                jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：创建Running命令：' + jsoncmd + '\n')

                """
                将命令生成消息体存入消息队列中
                """
                # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
                self.__MQ.put([ip, jsoncmd])
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')

                """
                初始化棋盘信息、对弈双方信息，准备开始对弈
                """
                # 初始化棋盘
                self.__APP.Board.Init_Board()
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】初始化棋盘完成，开始对弈！\n')
                # 初始化对弈双方的信息
                self.__APP.Assisted.Init_Assisted()
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】初始化当前执黑主机对弈双方信息，开始对弈！\n')
            else:
                """
                当前状态为不符合要求
                则返回连接失败RunningNo命令
                """

                """
                 创建命令
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：当前主机不是WhiteReady状态，返回RunningNo状态命令！\n')
                # 创建命令
                cmd = dict(cmd='RunningNo', args=dict(
                    nickname=self.__APP.Nickname))
                jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
                self.__log_print(self.getName() +
                                 '【命令】正在处理：创建RunningNo命令：' + jsoncmd + '\n')

                """
                将命令生成消息体存入消息队列中
                """
                # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
                self.__MQ.put([ip, jsoncmd])
                self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')

            """
            监听对象也放入__Outputs
            """
            # 加入select监听list outputs
            self.__Outputs.append(self.__UDP_Server)
            # 打印调试信息
            self.__log_print(self.getName() +
                             '【命令】正在处理：将socket连接加入outputs对象列表\n')
        elif cmd["cmd"] == 'ConnectNo':  # 连接请求失败（不包括超时失败）
            """
            方向：执白主机<----执黑主机
            执行方：执白主机
            """

            """
            连接失败，则从当前列表中删除当前连接的主机
            """
            # 打印调试信息
            self.__log_print(self.getName() +
                             '【命令】正在处理：连接失败则，从当前主机列表中清空此主机！\n')
            # 如果远端主机返回连接失败
            # 删除列表 并更新listbox
            self.__APP.Assisted.Black_List_Del(ip)
            # 打印调试信息
            self.__log_print(self.getName() + '【命令】正在处理：弹窗提醒！\n')
            # 弹窗提醒
            self.__APP.Assisted.show_errormsg(
                '连接失败', '连接远端主机失败，主机状态非BlackReady')
        elif cmd["cmd"] == 'RunningNo':  # Running失败
            """
            方向：执白主机---->执黑主机
            执行方：执黑主机
            """

            """
            判断当前对方主机是否来自于已经建立连接的主机
            是则还原状态和设置
            否则不处理
            """
            # 如果是当前连接的主机
            if self.__APP.OpponentIP == ip:
                """
                是连接的主机返回的信息
                则
                    修改本机状态为BlackReady
                    修改对弈状态为False
                    修改对手IP 还原None
                    修改对手昵称 还原None
                """
                # 还原执黑主机的状态到BlackReady
                self.__APP.AppState = AppState.BlackReady
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：远端主机Running失败，当前主机状态恢复BlackReady！\n')
                # 设置当前对弈的对手(IP)
                self.__APP.OpponentIP = None
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执黑，但执白主机running失败，设置对手IP:None\n')
                # 设置当前对弈状态
                self.__APP.IsPlay = False
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执黑，但执白主机running失败，所以关闭对弈状态\n')
                # 设置当前对弈的对手(昵称)
                self.__APP.OpponentNickname = None
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】当前执黑，设置对手昵称:None\n')
            else:
                """
                不是连接的主机返回的信息
                则不处理
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】非合法主机发送RunningNo命令不做任何处理\n')
        elif cmd["cmd"] == 'Running':  # Running
            """
            方向：执白主机---->执黑主机
            执行方：执黑主机
            """

            """
            判断当前对方主机是否来自于已经建立连接的主机
            是则初始化棋盘
            否则不处理
            """
            if self.__APP.OpponentIP == ip:
                """
                是连接的主机返回的信息
                则初始化棋盘、对弈双方信息
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：远端执白主机Running成功，正在初始化当前执黑主机棋盘信息！\n')
                # 初始化棋盘
                self.__APP.Board.Init_Board()
                # 初始化对弈双方的信息
                self.__APP.Assisted.Init_Assisted()
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：远端执白主机Running成功，正在初始化当前执黑主机对弈双方信息！\n')

                # 打印调试信息
                self.__log_print(self.getName() + '【命令】初始化棋盘完成，开始对弈！\n')
            else:
                """
                不是连接的主机返回的信息
                则不处理
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】非合法主机发送running命令不做任何处理\n')
        elif cmd["cmd"] == 'Play':  # Play
            """
            方向：执白主机<--->执黑主机
            执行方：双向
            """

            """
            判断当前对方主机是否来自于已经建立连接的主机
            是则执行落子操作
            否则不处理
            """
            if self.__APP.OpponentIP == ip:
                """
                是连接的主机返回的信息
                则
                    修改本机对弈状态为True
                    计算棋子坐标信息
                    落子
                """

                """
                设置对弈状态为True,准备我方落子
                """
                # 设置当前对弈状态
                self.__APP.IsPlay = True
                # 切换对弈状态身份提示
                self.__APP.Assisted.Switch_Identity()
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】正在处理：处理对方下子命令Play，正在绘制棋子！\n')

                """
                计算坐标，并落子
                """
                # 绘制棋子
                # x:%d,y:%d
                op = cmd["args"]
                x = op["x"]
                y = op["y"]
                # 落子
                self.__APP.Board.Other_Side_Play(x, y)
                # 打印调试信息
                self.__log_print(self.getName() + '【命令】对方下子成功，等待本方下子！\n')
            else:
                """
                不是连接的主机返回的信息
                则不处理
                """
                # 打印调试信息
                self.__log_print(self.getName() +
                                 '【命令】非合法主机发送running命令不做任何处理\n')
        else:  # 非法命令
            """
            方向：执白主机<--->执黑主机
            执行方：双向
            """
            # 打印调试信息
            self.__log_print(self.getName() + '【命令】非法命令\n')

    def __send_command(self, cmd, ip):
        """
            对外发送命令信息

            参数：
                cmd：命令主体
                ip：远端主机ip
        """

        # 获取远端主机的端口
        if self.__APP.LocalType == AppLocalType.Black:  # 当前为执黑主机，则发送消到执白主机端口
            port = self.__APP.WhitePort
        elif self.__APP.LocalType == AppLocalType.White:  # 当前为执白主机，则发送消到执黑主机端口:
            port = self.__APP.BlackPort
        else:  # 当前还没决定是执黑还是执白，退出发送
            return
        # 发送信息
        self.__UDP_Server.sendto(cmd.encode('utf-8'), (ip, port))

    def __log_print(self, t):
        """
            打印日志

            参数：
                t：要打印的文本
        """

        if self.__Debug:
            print(t)

    def Send_Point(self, ip, x, y):
        """
            发送下子（Play）坐标信息至对方

            参数：
                ip：远端主机IP
                x：棋盘X坐标索引0-14
                y：棋盘Y坐标索引0-14
        """

        """
        创建命令
        根据当前主机类型，获取端口信息
        然后调用__send_command()内部方法发送
        """

        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】主动发起命令 Play \n')
        # 创建命令
        cmd = dict(cmd='Play', args=dict(x=x, y=y))
        jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
        # 打印调试信息
        self.__log_print(self.getName() +
                         '【外部命令】正在处理：创建Play命令：' + jsoncmd + '\n')
        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】准备执行发送命令：' + jsoncmd + '\n')
        # 执行发送
        self.__send_command(jsoncmd, ip)
        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】发送完毕：' + jsoncmd + '\n')

    def Connect_Black(self, ip):
        """
            连接到执黑主机

            参数：
                ip：要连接的执黑主机IP
        """

        # # ######################################################
        # # 这是最初的想法
        # # 将命令加入message_queues队列
        # # 将socket加入output列表
        # # 触发 select 监听后发送
        # # 但是通过外部调用，无法触发 select 监听,应该与已经开始监听再次加入无法监听到，所以无法发送
        # # ######################################################
        # self.__log_print(self.getName() + '【命令】主动发起命令 Connect \n')
        # cmd = dict(cmd='Connect', cmdstr='Nickname:%s' %
        #            self.__APP.Nickname)
        # jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
        # self.__log_print(self.getName() + '【命令】正在处理：创建Connect命令：' + jsoncmd + '\n')

        # # 将待发送的命令存入队列中备用，ip和jsoncmd组成一个list对象存入。
        # self.__MQ.put([ip, jsoncmd])
        # self.__log_print(self.getName() + '【命令】正在处理：将命令加入待发送消息队列\n')
        # # 加入select监听list outputs
        # self.__Outputs.append(self.__UDP_Server)
        # self.__log_print(self.getName() + '【命令】正在处理：将socket连接加入outputs对象列表\n')
        # # ######################################################

        # # ######################################################
        # # 这是后来的处理办法
        # # 生成命令后直接调用socket对象发送
        # # ######################################################

        """
        创建命令
        根据当前主机类型，获取端口信息
        然后调用__send_command()内部方法发送
        """

        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】主动发起命令 Connect \n')
        # 创建命令
        cmd = dict(cmd='Connect', args=dict(nickname=self.__APP.Nickname))
        jsoncmd = json.dumps(cmd)  # 注意是转byte类型，发送时需要转码
        # 打印调试信息
        self.__log_print(self.getName() +
                         '【外部命令】正在处理：创建Connect命令：' + jsoncmd + '\n')
        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】准备执行发送命令：' + jsoncmd + '\n')
        # 执行发送
        self.__send_command(jsoncmd, ip)
        # 打印调试信息
        self.__log_print(self.getName() + '【外部命令】发送完毕：' + jsoncmd + '\n')

    def Close(self):
        """
            关闭当前线程的Socket连接对象
        """

        self.__clear_socket_list()
